上一篇我們用scrapy來爬Y Combinator Blog,它的結構不算太複雜,而且無須登入。
但如果今天需要爬取需要登入的網站資料該怎麼做呢?我們可以用python requests來實作。
本文感謝大數學堂的啟發,加上參考國外另一位Blogger分享的解法、簡化後整理而成
一般可能會透過加headers的方法來偽裝是一般使用者對目標網站發出requests,以防被網站block;如果遇到要登入的網站則是運用POST方法把自己的帳密傳送到login的url。
不過有些網站(像我們今天的範例 Anewstip),它在登入表單上有一個隱藏欄位叫"csrfmiddlewaretoken"(按F12對照一下表單欄位會發現),每次登入前會自動產生不同的亂數,登入的時候會跟著一起傳送過去;換句話說如果爬蟲時沒辦法帶到csrfmiddlewaretoken的值,便無法成功登入。
遇到這種狀況,我們需要在登入同時把該頁面的csrfmiddlewaretoken value存起來,連同自己的帳密做POST request。
.ipynb
,挑Python3的喔:(2和3語法不同,如果你挑2可能下面的code會error)user@ubuntu:NodeJS/tutorial/views$ jupyter notebook
還沒安裝jupyter notebook? →看anaconda(含jupyter nb)安裝教學
import requests
from lxml import html
from bs4 import BeautifulSoup as bs
LOGIN_URL
是目標網站登入表單的位址,因為後面的code我們會需要match到表單各個input name。USEREMAIL = 'yourname@example.com'
PASSWORD = '*******'
LOGIN_URL = 'https://anewstip.com/accounts/login/'
requests.session()
是幫助我們把這一次的request都算在同一個session裡,這樣我們第二次對登入頁面發request時,csrfmiddlewaretoken value才不會又重新產生。session_requests = requests.session()
result.text
(也就是網頁的結構)存起來解析,使用xpath
找到隱藏的csrfmiddlewaretoken值,先將它存起來。result = session_requests.get(LOGIN_URL)
tree = html.fromstring(result.text)
authenticity_token = list(set(tree.xpath('//input[@name="csrfmiddlewaretoken"]/@value')))[0]
這邊要特別注意,記得仔細觀察自己的目標網站cookie的變化;這次我的目標網站它的cookie值包含csrfmiddlewaretoken,因此要讓它隨著每次登入置換。
headers = {
'Connection': 'keep-alive',
'Content-Length': '103',
'Cache-Control': 'max-age=0',
'Origin': 'https://anewstip.com',
'Upgrade-Insecure-Requests': '1',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Referer': LOGIN_URL,
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'Cookie': '_ga=GA1.2.218269643.1513734952; _gid=GA1.2.1405785245.1513907212; csrftoken='+authenticity_token+'; sessionid=yvb8vq6m4katwmz76d0cnjubd29pdrdb; _gat=1'
}
payload = {
'email': USEREMAIL,
'password': PASSWORD,
'csrfmiddlewaretoken': authenticity_token
}
7. 以POST method登入
以剛剛啟動的session實例來發post request,才會在同一個session裡。後面記得帶上登入資訊及標頭參數
result = session_requests.post(LOGIN_URL, data = payload, headers = headers)
8. 以GET method爬取資料
如果你的網站只有一個頁面要爬,直接貼上就可以;但如果是需要換頁的,可以使用forLoop加頁碼參數。
若是動態載入的換頁,可以觀察network panel是否有規律的json檔名可以爬;再沒有就出動神器selenium吧(是我們後天的主題。)
注:通常如果使用loop連續對網站發出request,建議使用time套件設定sleep,降低IP被目標網站封鎖的風險。
result = session_requests.get(URL, headers = dict(referer = URL))
soup = bs(result.text, 'html.parser')
for link in soup.select('.info-name a'):
print('https://anewstip.com/'+link.get('href'))
...
def main():
for i in range(1, 11):
URL = 'https://anewstip.com/search/journalists/?q=privacy&page=' + str(i)
...
把上面全部combine起來:
import requests
from lxml import html
from bs4 import BeautifulSoup as bs
USEREMAIL = 'yourname@example.com'
PASSWORD = '*******'
LOGIN_URL = 'https://anewstip.com/accounts/login/'
def main():
for i in range(1, 11):
URL = 'https://anewstip.com/search/journalists/?q=privacy&page=' + str(i)
session_requests = requests.session()
result = session_requests.get(LOGIN_URL)
tree = html.fromstring(result.text)
authenticity_token = list(set(tree.xpath('//input[@name="csrfmiddlewaretoken"]/@value')))[0]
headers = {
'Connection': 'keep-alive',
'Content-Length': '103',
'Cache-Control': 'max-age=0',
'Origin': 'https://anewstip.com',
'Upgrade-Insecure-Requests': '1',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Referer': LOGIN_URL,
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'Cookie': '_ga=GA1.2.218269643.1513734952; _gid=GA1.2.1405785245.1513907212; csrftoken='+authenticity_token+'; sessionid=yvb8vq6m4katwmz76d0cnjubd29pdrdb; _gat=1'
}
payload = {
'email': USEREMAIL,
'password': PASSWORD,
'csrfmiddlewaretoken': authenticity_token
}
result = session_requests.post(LOGIN_URL, data = payload, headers = headers)
result = session_requests.get(URL, headers = dict(referer = URL))
soup = bs(result.text, 'html.parser')
for link in soup.select('.info-name a'):
print('https://anewstip.com/'+link.get('href'))
if __name__ == '__main__':
main()
跑起來之後output你會得到每個記者的profile url:
(在jupyter上是直接可以點的url~儘管我也沒有要直接使用它XD 我是拿來後續再跑scrapy抓個別url的social link~)
https://anewstip.com//journalist/profile/bsan805668820/
https://anewstip.com//journalist/profile/bsan805844974/
https://anewstip.com//journalist/profile/bsan743221732/
https://anewstip.com//journalist/profile/bsan91445200/
https://anewstip.com//journalist/profile/bsan767309356/
https://anewstip.com//journalist/profile/bsan871832038/
https://anewstip.com//journalist/profile/bsan83712574/
再次要強烈建議自己試做一次,不要直接複製貼上,因為像獲取token值和headers就不一定是每個網站都相同。儘管也有參考其他人作法,但一定有需要自己研究的成分在der。
明天會介紹遇到ajax動態載入資料的網站的爬蟲破解法~
未來:愛用者本人還是要不斷地預告selenium及xvfb XD —— 當今天連json檔案都沒法在network panel看到時該怎麼hack?
happy requesting!